package com.renu.jdependencyanalyser.freemarker.data;

import com.renu.jdependencyanalyser.cons.ProjectCons;
import com.renu.jdependencyanalyser.core.ProjectInfo;
import com.renu.jdependencyanalyser.data.ProjectDataCenter;
import com.renu.jdependencyanalyser.model.ClassModel;
import com.renu.jdependencyanalyser.model.JarModel;
import com.renu.jdependencyanalyser.model.JarNode;
import com.renu.jdependencyanalyser.model.MethodModel;
import com.renu.jdependencyanalyser.model.relation.ClassRelation;
import com.renu.jdependencyanalyser.model.relation.MethodRelation;
import com.renu.jdependencyanalyser.model.relation.Relation;

import java.util.*;

public class DependencyData {
    private List<Relation> rlts = new ArrayList<>();
    private  List<String> outerSigs = new ArrayList<>();

    private List<Integer> outerIds = new ArrayList<>();

    private List<IdRelation> idRlts = new ArrayList<>();

   private Set<Label> allLabels = new HashSet<>();


    private Map<String,Integer> allMap = new HashMap<>();

    private int cnt = 0;




    public DependencyData(ProjectInfo projectInfo, MethodModel methodModel, Set<String> dirOrIndirOuterMethodSigs) {
        if (dirOrIndirOuterMethodSigs.contains(methodModel.getMethodSig())){
            findMthdRltsAndOuterMthd(projectInfo,methodModel,0);
        }else{
            //allMthds.add(methodModel.getMethodSig());
            allMap.put(methodModel.getMethodSig(),cnt++);
            rlts.add(new MethodRelation(methodModel.getMethodSig(),"no outer method wa invoked"));
        }

        for (Relation mthdRlt : rlts) {
            String srcSig = mthdRlt.getSrc();
            String tgtSig = mthdRlt.getTgt();
            idRlts.add(new MthdIdRelation(allMap.get(srcSig), allMap.get(tgtSig)));
        }
        for (String key : allMap.keySet()) {
            allLabels.add(new MthdLabel(allMap.get(key),key));
        }
        for (String outerMthd : outerSigs) {
            outerIds.add(allMap.get(outerMthd));
        }

    }

    public DependencyData(ProjectInfo projectInfo, MethodModel methodModel) {
        this.outerSigs.add(methodModel.getMethodSig());
        findRltsFromOuterClass(methodModel, projectInfo);
        for (Relation mthdRlt : rlts) {
            String srcSig = mthdRlt.getSrc();
            String tgtSig = mthdRlt.getTgt();
            idRlts.add(new MthdIdRelation(allMap.get(srcSig), allMap.get(tgtSig)));
        }
        for (String key : allMap.keySet()) {
            allLabels.add(new MthdLabel(allMap.get(key),key));
        }
        for (String outerMthd : outerSigs) {
            outerIds.add(allMap.get(outerMthd));
        }
    }

    public DependencyData(ProjectInfo projectInfo, ClassModel classModel, int direction) {
        if (direction == ProjectCons.IN){
            this.outerSigs.add(classModel.getClsSig());
            findRltsFromOuterClass(classModel, projectInfo);
        }else if (direction == ProjectCons.OUT){
            findRltsFromInnerClass(classModel,projectInfo);
        }

        for (Relation rlt : rlts) {
            String srcSig = rlt.getSrc();
            String tgtSig = rlt.getTgt();
            idRlts.add(new IdRelation(allMap.get(srcSig), allMap.get(tgtSig)));
        }
        for (String key : allMap.keySet()) {
            allLabels.add(new Label(allMap.get(key),key));
        }
        for (String outerSig : outerSigs) {
            outerIds.add(allMap.get(outerSig));
        }
    }

    private Map<String, JarModel> borderJarMap = ProjectDataCenter.projectInfo.getBorderJarMap();


    private void findDependencyLibList(Set<String> borderJars, JarNode jarNode) {
        if (jarNode == null)
            return;
        if (borderJars.contains(jarNode.toJarSig())) {
            this.outerIds.add(jarNode.getId());
            JarModel jarModel = borderJarMap.get(jarNode.toJarSig());
            jarModel.setGroup(jarNode.getGroup());
            jarModel.setArtifact(jarNode.getArtifact());
            jarModel.setVersion(jarNode.getVersion());
        }
        this.allLabels.add(new Label(jarNode.getId(),jarNode.toString()));
        List<JarNode> children = jarNode.getChildren();
        for (JarNode child : children) {
            idRlts.add(new IdRelation(jarNode.getId(), child.getId()));
            findDependencyLibList(borderJars, child);
        }

    }
    public DependencyData(Set<String> borderJars, JarNode jarNode) {
        findDependencyLibList(borderJars, jarNode);
    }

    private void findRltsFromInnerClass(ClassModel classModel, ProjectInfo projectInfo) {
        if (classModel == null) {
            return;
        }
        String clsSig   = classModel.getClsSig();
        if (this.allMap.containsKey(clsSig)){
            return;
        }
        this.allMap.put(clsSig,cnt++);
        if (projectInfo.getOuterClassMap().containsKey(clsSig)){
            this.outerSigs.add(clsSig);
            return ;
        }
        Set<String> tgtClsSig = classModel.getTgtClsSig();
        for (String tgt : tgtClsSig) {
            this.rlts.add(new ClassRelation(clsSig,tgt));
            ClassModel tgtCls = projectInfo.getClassModel(tgt);
            if (tgtCls == null){//外部
                 tgtCls = projectInfo.getOuterClassMap().get(tgt);
            }
            findRltsFromInnerClass(tgtCls,projectInfo);

        }

    }

    private void findRltsFromOuterClass(ClassModel classModel, ProjectInfo projectInfo) {
        if (classModel == null){
            return ;
        }
        if (this.allMap.containsKey(classModel.getClsSig())){
            return;
        }
        this.allMap.put(classModel.getClsSig(),cnt++);

        Set<String> srcCLasss = classModel.getSrcClsSig();
        if (srcCLasss == null){
            return;
        }
        for (String srcCls : srcCLasss) {
            this.rlts.add(new ClassRelation(srcCls,classModel.getClsSig()));
            ClassModel src = projectInfo.getClassModel(srcCls);
            findRltsFromOuterClass(src,projectInfo);
        }
    }

    private void findRltsFromOuterClass(MethodModel method, ProjectInfo projectInfo) {
        if (method == null){
            return ;
        }
        this.allMap.put(method.getMethodSig(),cnt++);

        Set<String> srcMethods = method.getSrcMethods();
       if (srcMethods == null){
           return;
       }
        for (String srcMethod : srcMethods) {
            this.rlts.add(new MethodRelation(srcMethod,method.getMethodSig()));
            MethodModel src = projectInfo.getMethod(srcMethod);
            findRltsFromOuterClass(src,projectInfo);
        }

    }


    private boolean findMthdRltsAndOuterMthd(ProjectInfo projectInfo, MethodModel methodModel,int depth) {
        if (depth == ProjectCons.MAX_DPTH_MTHD){
            return false;
        }
        if (projectInfo.getOuterMethod(methodModel.getMethodSig())!= null){
            this.outerSigs.add(methodModel.getMethodSig());
            //this.allMthds.add(methodModel.getMethodSig());
            this.allMap.put(methodModel.getMethodSig(),cnt++);
            return true;
        }
        Set<String> tgtMethods = methodModel.getTgtMethods();
        boolean res= false;
        for (String tgtMethod : tgtMethods) {
            if (projectInfo.getOuterMethod(tgtMethod)!= null){ // 出类为外部类api
                this.rlts.add(new MethodRelation(methodModel.getMethodSig(),tgtMethod));
                this.allMap.put(tgtMethod,cnt++);
                this.outerSigs.add(tgtMethod);
                res |= true;
            }else{
                if (findMthdRltsAndOuterMthd(projectInfo,projectInfo.getMethod(tgtMethod),depth+1)){
                    this.rlts.add(new MethodRelation(methodModel.getMethodSig(),tgtMethod));
                    res |= true;
                }
            }
            if (res){
                this.allMap.put(methodModel.getMethodSig(),cnt++);
            }

        }
        return res;


    }


    public List<Integer> getOuterIds() {
        return outerIds;
    }

    public List<IdRelation> getIdRlts() {
        return idRlts;
    }

    public Set<Label> getAllLabels() {
        return allLabels;
    }

    public List<String> getOuterSigs() {
        return outerSigs;
    }

    public Map<String, Integer> getAllMap() {
        return allMap;
    }
}
